//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.RuntimeServices.Monitoring
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Threading;
    using System.Workflow.ComponentModel;
    using System.Workflow.Runtime.Tracking;
    using Microsoft.Research.eResearch.Blackboard.Common;
    using Microsoft.Research.eResearch.RuntimeServices.Blackboard;

    public class MonitoringChannel : TrackingChannel
    {
        private IServiceProvider serviceProvider;

        private MonitoringService monitoringService;

        private TrackingParameters trackingParameters;

        // Dictionary<ActivityName, Dictionary<ConceptName,{Timer,IResourceMonitor}>>
        private List<MonitorRecord> monitorLookupTable = new List<MonitorRecord>();

        public MonitoringChannel(IServiceProvider serviceProvider, MonitoringService monitoringService, TrackingParameters trackingParameters)
        {
            this.serviceProvider = serviceProvider;
            this.monitoringService = monitoringService;
            this.trackingParameters = trackingParameters;
        }

        protected override void InstanceCompletedOrTerminated()
        {
        }

        protected override void Send(TrackingRecord record)
        {
            ActivityTrackingRecord atr = record as ActivityTrackingRecord;
            if (atr != null)
            {
                switch (atr.ExecutionStatus)
                {
                    case ActivityExecutionStatus.Executing:
                        // start the timers
                        this.StartTimers(atr.QualifiedName, atr.ActivityType);
                        break;
                    case ActivityExecutionStatus.Faulting:
                    case ActivityExecutionStatus.Canceling:
                    case ActivityExecutionStatus.Closed:
                    case ActivityExecutionStatus.Compensating:
                        // stop the timers
                        this.StopTimers(atr.QualifiedName);
                        break;
                    case ActivityExecutionStatus.Initialized: 
                    default:
                        // do nothing
                        break;
                }
            }

            WorkflowTrackingRecord wtr = record as WorkflowTrackingRecord;
            if (wtr != null)
            {
                // Note: The following logic is required to support terminate function.
                // On Workflow Terminate event we should stop all the timers
                if (wtr.TrackingWorkflowEvent == TrackingWorkflowEvent.Terminated)
                {
                    List<MonitorRecord> monitorRecords = monitorLookupTable.ToList();
                
                    foreach (MonitorRecord rec in monitorRecords)
                    {
                        rec.Timer.Dispose();
                        rec.ResourceMonitor.Teardown();
                        monitorLookupTable.Remove(rec);
                    }
                }
            }
        }

        private void StartTimers(string activityName, Type activityType)
        {
            JobExecutionService jobIdService = serviceProvider.GetJobExecutionService();

            // Get the list of currently-subscribed concepts
            IBlackboardService blackboard = serviceProvider.GetBlackboardService();
            List<string> subscribedConcepts = 
                blackboard
                .LastSubscriptionProfile
                .Select(item => item.Name)
                .ToList();

            // Keep the intersection of [the currently subscribed concepts] and [the concepts that we are capable of monitoring]
            List<string> subscribedMonitorConcepts =
                monitoringService.AvailableMonitorConcepts
                .Intersect(subscribedConcepts)
                .ToList();

            // Start timers for each resource monitor
            foreach (string concept in subscribedMonitorConcepts)
            {
                // Is there already a record in the lookup table?
                bool alreadyCreatedTimer =
                    monitorLookupTable
                    .Where(record => (record.ActivityName == activityName) && (record.ConceptName == concept))
                    .Count() > 0;

                // If not, then create it
                if (!alreadyCreatedTimer)
                {
                    Type monitorType = monitoringService.FindMonitorTypeForConcept(concept);
                    IResourceMonitor monitor = (IResourceMonitor)Activator.CreateInstance(monitorType);
                    monitor.Setup();

                    MonitorTimerState monitorTimerState = new MonitorTimerState(
                        blackboard,
                        monitor,
                        concept,
                        trackingParameters.InstanceId.ToString(),
                        activityName,
                        activityType.AssemblyQualifiedName);

                    Timer timer = TimerHelper.Create(
                        (long)monitor.MonitorTimeSpan.TotalMilliseconds,
                        monitorTimerState,
                        (state) =>
                        {
                            if (state.Monitor.MonitorResource(ActivityMonitorEvent.Executing))
                            {
                                string resource = state.Monitor.GetResource();

                                BlackboardMessage message = new BlackboardMessage();
                                message.Add(state.Concept, resource);
                                message.Add("trident:InstanceId", state.WorkflowInstanceId);
                                message.Add("trident:ActivityName", state.ActivityName);
                                message.Add("trident:ActivityType", state.ActivityType);
                                message.Add("trident:EventTimestamp", DateTime.Now.ToString("o", CultureInfo.CurrentCulture));
                                
                                // trident:JobId concept value is retrieved from runtime service not from TrackingRecord.
                                // So not using ontology lookup service.
                                message.Add(jobIdService.JobIdConcept.Concept, jobIdService.JobIdConcept.Value);

                                state.BlackboardService.PublishMessage(message);
                            }
                        });

                    MonitorRecord record = new MonitorRecord(activityName, activityType, concept, timer, monitor);
                    monitorLookupTable.Add(record);
                }
            }

        }

        private void StopTimers(string activityName)
        {
            List<MonitorRecord> monitorRecords = monitorLookupTable
                .Where(record => (record.ActivityName == activityName))
                .ToList();

            foreach (MonitorRecord record in monitorRecords)
            {
                record.Timer.Dispose();
                record.ResourceMonitor.Teardown();
                monitorLookupTable.Remove(record);
            }
        }
    }
}
